Conditions | 62 |
Paths | > 20000 |
Total Lines | 365 |
Code Lines | 199 |
Lines | 0 |
Ratio | 0 % |
Changes | 0 |
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like minimatch.js ➔ parse often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
1 | module.exports = minimatch |
||
271 | function parse (pattern, isSub) { |
||
272 | if (pattern.length > 1024 * 64) { |
||
273 | throw new TypeError('pattern is too long') |
||
274 | } |
||
275 | |||
276 | var options = this.options |
||
277 | |||
278 | // shortcuts |
||
279 | if (!options.noglobstar && pattern === '**') return GLOBSTAR |
||
280 | if (pattern === '') return '' |
||
281 | |||
282 | var re = '' |
||
283 | var hasMagic = !!options.nocase |
||
284 | var escaping = false |
||
285 | // ? => one single character |
||
286 | var patternListStack = [] |
||
287 | var negativeLists = [] |
||
288 | var stateChar |
||
289 | var inClass = false |
||
290 | var reClassStart = -1 |
||
291 | var classStart = -1 |
||
292 | // . and .. never match anything that doesn't start with ., |
||
293 | // even when options.dot is set. |
||
294 | var patternStart = pattern.charAt(0) === '.' ? '' // anything |
||
295 | // not (start or / followed by . or .. followed by / or end) |
||
296 | : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))' |
||
297 | : '(?!\\.)' |
||
298 | var self = this |
||
299 | |||
300 | function clearStateChar () { |
||
301 | if (stateChar) { |
||
302 | // we had some state-tracking character |
||
303 | // that wasn't consumed by this pass. |
||
304 | switch (stateChar) { |
||
305 | case '*': |
||
306 | re += star |
||
307 | hasMagic = true |
||
308 | break |
||
309 | case '?': |
||
310 | re += qmark |
||
311 | hasMagic = true |
||
312 | break |
||
313 | default: |
||
314 | re += '\\' + stateChar |
||
315 | break |
||
316 | } |
||
317 | self.debug('clearStateChar %j %j', stateChar, re) |
||
318 | stateChar = false |
||
319 | } |
||
320 | } |
||
321 | |||
322 | for (var i = 0, len = pattern.length, c |
||
323 | ; (i < len) && (c = pattern.charAt(i)) |
||
324 | ; i++) { |
||
325 | this.debug('%s\t%s %s %j', pattern, i, re, c) |
||
326 | |||
327 | // skip over any that are escaped. |
||
328 | if (escaping && reSpecials[c]) { |
||
329 | re += '\\' + c |
||
330 | escaping = false |
||
331 | continue |
||
332 | } |
||
333 | |||
334 | switch (c) { |
||
335 | case '/': |
||
336 | // completely not allowed, even escaped. |
||
337 | // Should already be path-split by now. |
||
338 | return false |
||
339 | |||
340 | case '\\': |
||
341 | clearStateChar() |
||
342 | escaping = true |
||
343 | continue |
||
344 | |||
345 | // the various stateChar values |
||
346 | // for the "extglob" stuff. |
||
347 | case '?': |
||
348 | case '*': |
||
349 | case '+': |
||
350 | case '@': |
||
351 | case '!': |
||
352 | this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c) |
||
353 | |||
354 | // all of those are literals inside a class, except that |
||
355 | // the glob [!a] means [^a] in regexp |
||
356 | if (inClass) { |
||
357 | this.debug(' in class') |
||
358 | if (c === '!' && i === classStart + 1) c = '^' |
||
359 | re += c |
||
360 | continue |
||
361 | } |
||
362 | |||
363 | // if we already have a stateChar, then it means |
||
364 | // that there was something like ** or +? in there. |
||
365 | // Handle the stateChar, then proceed with this one. |
||
366 | self.debug('call clearStateChar %j', stateChar) |
||
367 | clearStateChar() |
||
368 | stateChar = c |
||
369 | // if extglob is disabled, then +(asdf|foo) isn't a thing. |
||
370 | // just clear the statechar *now*, rather than even diving into |
||
371 | // the patternList stuff. |
||
372 | if (options.noext) clearStateChar() |
||
373 | continue |
||
374 | |||
375 | case '(': |
||
376 | if (inClass) { |
||
377 | re += '(' |
||
378 | continue |
||
379 | } |
||
380 | |||
381 | if (!stateChar) { |
||
382 | re += '\\(' |
||
383 | continue |
||
384 | } |
||
385 | |||
386 | patternListStack.push({ |
||
387 | type: stateChar, |
||
388 | start: i - 1, |
||
389 | reStart: re.length, |
||
390 | open: plTypes[stateChar].open, |
||
391 | close: plTypes[stateChar].close |
||
392 | }) |
||
393 | // negation is (?:(?!js)[^/]*) |
||
394 | re += stateChar === '!' ? '(?:(?!(?:' : '(?:' |
||
395 | this.debug('plType %j %j', stateChar, re) |
||
396 | stateChar = false |
||
397 | continue |
||
398 | |||
399 | case ')': |
||
400 | if (inClass || !patternListStack.length) { |
||
401 | re += '\\)' |
||
402 | continue |
||
403 | } |
||
404 | |||
405 | clearStateChar() |
||
406 | hasMagic = true |
||
407 | var pl = patternListStack.pop() |
||
408 | // negation is (?:(?!js)[^/]*) |
||
409 | // The others are (?:<pattern>)<type> |
||
410 | re += pl.close |
||
411 | if (pl.type === '!') { |
||
412 | negativeLists.push(pl) |
||
413 | } |
||
414 | pl.reEnd = re.length |
||
415 | continue |
||
416 | |||
417 | case '|': |
||
418 | if (inClass || !patternListStack.length || escaping) { |
||
419 | re += '\\|' |
||
420 | escaping = false |
||
421 | continue |
||
422 | } |
||
423 | |||
424 | clearStateChar() |
||
425 | re += '|' |
||
426 | continue |
||
427 | |||
428 | // these are mostly the same in regexp and glob |
||
429 | case '[': |
||
430 | // swallow any state-tracking char before the [ |
||
431 | clearStateChar() |
||
432 | |||
433 | if (inClass) { |
||
434 | re += '\\' + c |
||
435 | continue |
||
436 | } |
||
437 | |||
438 | inClass = true |
||
439 | classStart = i |
||
440 | reClassStart = re.length |
||
441 | re += c |
||
442 | continue |
||
443 | |||
444 | case ']': |
||
445 | // a right bracket shall lose its special |
||
446 | // meaning and represent itself in |
||
447 | // a bracket expression if it occurs |
||
448 | // first in the list. -- POSIX.2 2.8.3.2 |
||
449 | if (i === classStart + 1 || !inClass) { |
||
450 | re += '\\' + c |
||
451 | escaping = false |
||
452 | continue |
||
453 | } |
||
454 | |||
455 | // handle the case where we left a class open. |
||
456 | // "[z-a]" is valid, equivalent to "\[z-a\]" |
||
457 | if (inClass) { |
||
458 | // split where the last [ was, make sure we don't have |
||
459 | // an invalid re. if so, re-walk the contents of the |
||
460 | // would-be class to re-translate any characters that |
||
461 | // were passed through as-is |
||
462 | // TODO: It would probably be faster to determine this |
||
463 | // without a try/catch and a new RegExp, but it's tricky |
||
464 | // to do safely. For now, this is safe and works. |
||
465 | var cs = pattern.substring(classStart + 1, i) |
||
466 | try { |
||
467 | RegExp('[' + cs + ']') |
||
468 | } catch (er) { |
||
469 | // not a valid class! |
||
470 | var sp = this.parse(cs, SUBPARSE) |
||
471 | re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' |
||
472 | hasMagic = hasMagic || sp[1] |
||
473 | inClass = false |
||
474 | continue |
||
475 | } |
||
476 | } |
||
477 | |||
478 | // finish up the class. |
||
479 | hasMagic = true |
||
480 | inClass = false |
||
481 | re += c |
||
482 | continue |
||
483 | |||
484 | default: |
||
485 | // swallow any state char that wasn't consumed |
||
486 | clearStateChar() |
||
487 | |||
488 | if (escaping) { |
||
489 | // no need |
||
490 | escaping = false |
||
491 | } else if (reSpecials[c] |
||
492 | && !(c === '^' && inClass)) { |
||
493 | re += '\\' |
||
494 | } |
||
495 | |||
496 | re += c |
||
497 | |||
498 | } // switch |
||
499 | } // for |
||
500 | |||
501 | // handle the case where we left a class open. |
||
502 | // "[abc" is valid, equivalent to "\[abc" |
||
503 | if (inClass) { |
||
504 | // split where the last [ was, and escape it |
||
505 | // this is a huge pita. We now have to re-walk |
||
506 | // the contents of the would-be class to re-translate |
||
507 | // any characters that were passed through as-is |
||
508 | cs = pattern.substr(classStart + 1) |
||
509 | sp = this.parse(cs, SUBPARSE) |
||
510 | re = re.substr(0, reClassStart) + '\\[' + sp[0] |
||
511 | hasMagic = hasMagic || sp[1] |
||
512 | } |
||
513 | |||
514 | // handle the case where we had a +( thing at the *end* |
||
515 | // of the pattern. |
||
516 | // each pattern list stack adds 3 chars, and we need to go through |
||
517 | // and escape any | chars that were passed through as-is for the regexp. |
||
518 | // Go through and escape them, taking care not to double-escape any |
||
519 | // | chars that were already escaped. |
||
520 | for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { |
||
521 | var tail = re.slice(pl.reStart + pl.open.length) |
||
522 | this.debug('setting tail', re, pl) |
||
523 | // maybe some even number of \, then maybe 1 \, followed by a | |
||
524 | tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) { |
||
525 | if (!$2) { |
||
526 | // the | isn't already escaped, so escape it. |
||
527 | $2 = '\\' |
||
528 | } |
||
529 | |||
530 | // need to escape all those slashes *again*, without escaping the |
||
531 | // one that we need for escaping the | character. As it works out, |
||
532 | // escaping an even number of slashes can be done by simply repeating |
||
533 | // it exactly after itself. That's why this trick works. |
||
534 | // |
||
535 | // I am sorry that you have to see this. |
||
536 | return $1 + $1 + $2 + '|' |
||
537 | }) |
||
538 | |||
539 | this.debug('tail=%j\n %s', tail, tail, pl, re) |
||
540 | var t = pl.type === '*' ? star |
||
541 | : pl.type === '?' ? qmark |
||
542 | : '\\' + pl.type |
||
543 | |||
544 | hasMagic = true |
||
545 | re = re.slice(0, pl.reStart) + t + '\\(' + tail |
||
546 | } |
||
547 | |||
548 | // handle trailing things that only matter at the very end. |
||
549 | clearStateChar() |
||
550 | if (escaping) { |
||
551 | // trailing \\ |
||
552 | re += '\\\\' |
||
553 | } |
||
554 | |||
555 | // only need to apply the nodot start if the re starts with |
||
556 | // something that could conceivably capture a dot |
||
557 | var addPatternStart = false |
||
558 | switch (re.charAt(0)) { |
||
559 | case '.': |
||
560 | case '[': |
||
561 | case '(': addPatternStart = true |
||
562 | } |
||
563 | |||
564 | // Hack to work around lack of negative lookbehind in JS |
||
565 | // A pattern like: *.!(x).!(y|z) needs to ensure that a name |
||
566 | // like 'a.xyz.yz' doesn't match. So, the first negative |
||
567 | // lookahead, has to look ALL the way ahead, to the end of |
||
568 | // the pattern. |
||
569 | for (var n = negativeLists.length - 1; n > -1; n--) { |
||
570 | var nl = negativeLists[n] |
||
571 | |||
572 | var nlBefore = re.slice(0, nl.reStart) |
||
573 | var nlFirst = re.slice(nl.reStart, nl.reEnd - 8) |
||
574 | var nlLast = re.slice(nl.reEnd - 8, nl.reEnd) |
||
575 | var nlAfter = re.slice(nl.reEnd) |
||
576 | |||
577 | nlLast += nlAfter |
||
578 | |||
579 | // Handle nested stuff like *(*.js|!(*.json)), where open parens |
||
580 | // mean that we should *not* include the ) in the bit that is considered |
||
581 | // "after" the negated section. |
||
582 | var openParensBefore = nlBefore.split('(').length - 1 |
||
583 | var cleanAfter = nlAfter |
||
584 | for (i = 0; i < openParensBefore; i++) { |
||
585 | cleanAfter = cleanAfter.replace(/\)[+*?]?/, '') |
||
586 | } |
||
587 | nlAfter = cleanAfter |
||
588 | |||
589 | var dollar = '' |
||
590 | if (nlAfter === '' && isSub !== SUBPARSE) { |
||
591 | dollar = '$' |
||
592 | } |
||
593 | var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast |
||
594 | re = newRe |
||
595 | } |
||
596 | |||
597 | // if the re is not "" at this point, then we need to make sure |
||
598 | // it doesn't match against an empty path part. |
||
599 | // Otherwise a/* will match a/, which it should not. |
||
600 | if (re !== '' && hasMagic) { |
||
601 | re = '(?=.)' + re |
||
602 | } |
||
603 | |||
604 | if (addPatternStart) { |
||
605 | re = patternStart + re |
||
606 | } |
||
607 | |||
608 | // parsing just a piece of a larger pattern. |
||
609 | if (isSub === SUBPARSE) { |
||
610 | return [re, hasMagic] |
||
611 | } |
||
612 | |||
613 | // skip the regexp for non-magical patterns |
||
614 | // unescape anything in it, though, so that it'll be |
||
615 | // an exact match against a file etc. |
||
616 | if (!hasMagic) { |
||
617 | return globUnescape(pattern) |
||
618 | } |
||
619 | |||
620 | var flags = options.nocase ? 'i' : '' |
||
621 | try { |
||
622 | var regExp = new RegExp('^' + re + '$', flags) |
||
623 | } catch (er) { |
||
624 | // If it was an invalid regular expression, then it can't match |
||
625 | // anything. This trick looks for a character after the end of |
||
626 | // the string, which is of course impossible, except in multi-line |
||
627 | // mode, but it's not a /m regex. |
||
628 | return new RegExp('$.') |
||
629 | } |
||
630 | |||
631 | regExp._glob = pattern |
||
632 | regExp._src = re |
||
633 | |||
634 | return regExp |
||
635 | } |
||
636 | |||
924 |